﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using DynamicViewModel.Tests.TestTypes;
using Microsoft.CSharp.RuntimeBinder;
using Xunit;

namespace DynamicViewModel.Tests
{
    public sealed class DynamicViewModelOfTTest : IUseFixture<ModelFixture>
    {
        [Fact]
        public void GetPropertyValues()
        {
            dynamic viewModel = new DynamicViewModel<ContactDetails>(Model);

            Assert.Equal("First", viewModel.FirstName);
            Assert.Equal("Last", viewModel.LastName);
            Assert.Equal("First Last", viewModel.FullName);
        }

        [Fact]
        public void SetPropertyValues()
        {
            var model = new ContactDetails()
            {
                FirstName = "First",
                LastName = "Last"
            };

            dynamic viewModel = new DynamicViewModel<ContactDetails>(model);
            viewModel.FirstName = "Nikolaos";

            Assert.Equal("Nikolaos Last", viewModel.FullName);
        }

        [Fact]
        public void InvalidProperty()
        {
            var model = new ContactDetails()
            {
                FirstName = "First",
                LastName = "Last"
            };

            dynamic viewModel = new DynamicViewModel<ContactDetails>(model);
            Assert.Throws<RuntimeBinderException>(() =>
            {
                var value = viewModel.NonExistingProperty;
            });
        }

        [Fact]
        public void InvokeMethods()
        {
            var model = new ContactDetails()
            {
                FirstName = "First",
                LastName = "Last"
            };

            dynamic viewModel = new DynamicViewModel<ContactDetails>(model);
            viewModel.ClearFullName();

            Assert.Equal(String.Empty, viewModel.FirstName);
            Assert.Equal(String.Empty, viewModel.LastName);
            Assert.True(String.IsNullOrWhiteSpace(viewModel.FullName));
        }

        [Fact]
        public void InvokeMethodsWithArgs()
        {
            Int32 age = 26;

            dynamic viewModel = new DynamicViewModel<ContactDetails>(Model);
            viewModel.SetAge(age);

            Assert.Equal(age, viewModel.Age);
        }

        [Fact]
        public void NotifyPropertyChanged()
        {
            EventWaitHandle done = new ManualResetEvent(false);

            dynamic viewModel = new DynamicViewModel<ContactDetails>(Model);
            ((INotifyPropertyChanged)viewModel).PropertyChanged += (sender, e) =>
            {
                Assert.Equal("Age", e.PropertyName);
                done.Set();
            };
            viewModel.SetAge(26);

            done.WaitOne();
        }

        [Fact]
        public void NotifyPropertyChanged_MoreThanOneProps()
        {
            Int32 numChangedProp = 0;
            EventWaitHandle done = new ManualResetEvent(false);

            var model = new ContactDetails()
            {
                FirstName = "First",
                LastName = "Last"
            };

            ThreadPool.QueueUserWorkItem(delegate
            {
                var changedProps = new Collection<String>();
                dynamic viewModel = new DynamicViewModel<ContactDetails>(model);
                ((INotifyPropertyChanged)viewModel).PropertyChanged += (sender, e) =>
                {
                    changedProps.Add(e.PropertyName);
                    if (Interlocked.Increment(ref numChangedProp) == 2)
                    {
                        Assert.True(changedProps.Contains("FirstName"));
                        Assert.True(changedProps.Contains("FullName"));
                        done.Set();
                    }
                };
                viewModel.FirstName = "Nikolaos";
            }, null);

            done.WaitOne();
        }

        private ContactDetails Model { get; set; }

        #region IUseFixture<ModelFixture> Members

        public void SetFixture(ModelFixture data)
        {
            Model = data.Model;
        }

        #endregion
    }
}
